"""
Audio effects processing for MD2PPT.

Provides audio effects like reverb, echo, compression, etc.
"""

import asyncio
import os
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
import tempfile

from ...utils.exceptions import MD2PPTError


@dataclass
class EffectConfig:
    """Configuration for audio effects."""
    effect_type: str
    parameters: Dict[str, Any]
    enabled: bool = True
    
    def to_ffmpeg_filter(self) -> str:
        """Convert effect configuration to FFmpeg filter string."""
        if not self.enabled:
            return ""
        
        if self.effect_type == "reverb":
            # Room impulse reverb
            room_size = self.parameters.get("room_size", 0.5)  # 0.0 to 1.0
            damping = self.parameters.get("damping", 0.5)
            wet_level = self.parameters.get("wet_level", 0.3)
            dry_level = self.parameters.get("dry_level", 0.7)
            
            return f"aecho=0.8:0.9:{int(room_size * 1000)}:{wet_level}"
        
        elif self.effect_type == "echo":
            delay = self.parameters.get("delay", 0.5)  # seconds
            decay = self.parameters.get("decay", 0.6)  # 0.0 to 1.0
            
            return f"aecho={decay}:0.9:{int(delay * 1000)}:0.5"
        
        elif self.effect_type == "compressor":
            threshold = self.parameters.get("threshold", -20)  # dB
            ratio = self.parameters.get("ratio", 4)  # 1:N
            attack = self.parameters.get("attack", 5)  # ms
            release = self.parameters.get("release", 50)  # ms
            makeup = self.parameters.get("makeup", 2)  # dB
            
            return f"acompressor=threshold={threshold}dB:ratio={ratio}:attack={attack}:release={release}:makeup={makeup}dB"
        
        elif self.effect_type == "noise_gate":
            threshold = self.parameters.get("threshold", -50)  # dB
            ratio = self.parameters.get("ratio", 2)
            attack = self.parameters.get("attack", 20)  # ms
            release = self.parameters.get("release", 250)  # ms
            
            return f"agate=threshold={threshold}dB:ratio={ratio}:attack={attack}:release={release}"
        
        elif self.effect_type == "eq":
            # 3-band EQ
            low_gain = self.parameters.get("low_gain", 0)  # dB
            mid_gain = self.parameters.get("mid_gain", 0)  # dB
            high_gain = self.parameters.get("high_gain", 0)  # dB
            
            filters = []
            if low_gain != 0:
                filters.append(f"bass=g={low_gain}")
            if high_gain != 0:
                filters.append(f"treble=g={high_gain}")
            
            return ",".join(filters) if filters else ""
        
        elif self.effect_type == "chorus":
            in_gain = self.parameters.get("in_gain", 0.4)
            out_gain = self.parameters.get("out_gain", 0.4)
            delays = self.parameters.get("delays", "60,40")
            decays = self.parameters.get("decays", "0.5,0.25")
            speeds = self.parameters.get("speeds", "0.6,0.32")
            depths = self.parameters.get("depths", "10,25")
            
            return f"chorus={in_gain}:{out_gain}:{delays}:{decays}:{speeds}:{depths}"
        
        elif self.effect_type == "normalize":
            peak = self.parameters.get("peak", -1.0)  # dB
            return f"loudnorm=I=-16:TP={peak}:LRA=11"
        
        elif self.effect_type == "highpass":
            frequency = self.parameters.get("frequency", 80)  # Hz
            return f"highpass=f={frequency}"
        
        elif self.effect_type == "lowpass":
            frequency = self.parameters.get("frequency", 15000)  # Hz
            return f"lowpass=f={frequency}"
        
        else:
            return ""


class AudioEffects:
    """Audio effects processor using FFmpeg."""
    
    def __init__(self):
        """Initialize audio effects processor."""
        self._temp_files: List[str] = []
    
    async def apply_effects(self, input_path: str, 
                          effects: List[EffectConfig],
                          output_path: Optional[str] = None) -> str:
        """
        Apply audio effects to an audio file.
        
        Args:
            input_path: Input audio file
            effects: List of effects to apply
            output_path: Output file path (auto-generated if None)
            
        Returns:
            Path to processed audio file
        """
        if not os.path.exists(input_path):
            raise AudioEffectsError(f"Input file not found: {input_path}")
        
        if output_path is None:
            output_path = self._generate_temp_path("processed_audio.mp3")
        
        try:
            # Build FFmpeg command
            cmd = await self._build_effects_command(input_path, effects, output_path)
            
            # Execute FFmpeg
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await process.communicate()
            
            if process.returncode != 0:
                error_msg = stderr.decode() if stderr else "Unknown FFmpeg error"
                raise AudioEffectsError(f"Effects processing failed: {error_msg}")
            
            if not os.path.exists(output_path):
                raise AudioEffectsError("Output file was not created")
            
            return output_path
            
        except Exception as e:
            await self._cleanup_temp_files()
            if isinstance(e, AudioEffectsError):
                raise
            else:
                raise AudioEffectsError(f"Audio effects processing failed: {e}")
    
    async def enhance_voice(self, input_path: str,
                          output_path: Optional[str] = None,
                          preset: str = "default") -> str:
        """
        Apply voice enhancement effects.
        
        Args:
            input_path: Input voice audio file
            output_path: Output file path
            preset: Enhancement preset (default, radio, podcast, etc.)
            
        Returns:
            Path to enhanced audio file
        """
        presets = {
            "default": [
                EffectConfig("highpass", {"frequency": 80}),
                EffectConfig("compressor", {"threshold": -18, "ratio": 3, "attack": 3, "release": 30}),
                EffectConfig("eq", {"low_gain": -2, "mid_gain": 2, "high_gain": 1}),
                EffectConfig("normalize", {"peak": -1.0})
            ],
            "radio": [
                EffectConfig("highpass", {"frequency": 100}),
                EffectConfig("lowpass", {"frequency": 8000}),
                EffectConfig("compressor", {"threshold": -16, "ratio": 4, "attack": 2, "release": 20}),
                EffectConfig("eq", {"low_gain": -3, "mid_gain": 3, "high_gain": -1}),
                EffectConfig("normalize", {"peak": -0.5})
            ],
            "podcast": [
                EffectConfig("noise_gate", {"threshold": -45, "ratio": 3}),
                EffectConfig("highpass", {"frequency": 85}),
                EffectConfig("compressor", {"threshold": -20, "ratio": 3, "attack": 5, "release": 50}),
                EffectConfig("eq", {"low_gain": -1, "mid_gain": 1, "high_gain": 0}),
                EffectConfig("normalize", {"peak": -1.0})
            ],
            "warm": [
                EffectConfig("highpass", {"frequency": 75}),
                EffectConfig("compressor", {"threshold": -22, "ratio": 2.5, "attack": 10, "release": 100}),
                EffectConfig("eq", {"low_gain": 1, "mid_gain": 0, "high_gain": -1}),
                EffectConfig("chorus", {"in_gain": 0.2, "out_gain": 0.1}),
                EffectConfig("normalize", {"peak": -1.0})
            ]
        }
        
        effects = presets.get(preset, presets["default"])
        return await self.apply_effects(input_path, effects, output_path)
    
    async def add_ambient_reverb(self, input_path: str,
                               room_type: str = "small_room",
                               output_path: Optional[str] = None) -> str:
        """
        Add ambient reverb to audio.
        
        Args:
            input_path: Input audio file
            room_type: Type of room reverb (small_room, large_room, hall, etc.)
            output_path: Output file path
            
        Returns:
            Path to audio with reverb
        """
        room_configs = {
            "small_room": {"room_size": 0.3, "damping": 0.7, "wet_level": 0.2, "dry_level": 0.8},
            "medium_room": {"room_size": 0.5, "damping": 0.5, "wet_level": 0.3, "dry_level": 0.7},
            "large_room": {"room_size": 0.7, "damping": 0.3, "wet_level": 0.4, "dry_level": 0.6},
            "hall": {"room_size": 0.9, "damping": 0.2, "wet_level": 0.5, "dry_level": 0.5},
            "cathedral": {"room_size": 1.0, "damping": 0.1, "wet_level": 0.6, "dry_level": 0.4}
        }
        
        config = room_configs.get(room_type, room_configs["small_room"])
        effects = [EffectConfig("reverb", config)]
        
        return await self.apply_effects(input_path, effects, output_path)
    
    async def _build_effects_command(self, input_path: str,
                                   effects: List[EffectConfig],
                                   output_path: str) -> List[str]:
        """Build FFmpeg command for applying effects."""
        cmd = ["ffmpeg", "-y", "-i", input_path]
        
        # Build filter chain
        filters = []
        for effect in effects:
            if effect.enabled:
                filter_str = effect.to_ffmpeg_filter()
                if filter_str:
                    filters.append(filter_str)
        
        if filters:
            filter_complex = ",".join(filters)
            cmd.extend(["-af", filter_complex])
        
        # Output settings
        cmd.extend(["-c:a", "libmp3lame", "-b:a", "192k"])
        cmd.append(output_path)
        
        return cmd
    
    def _generate_temp_path(self, filename: str) -> str:
        """Generate temporary file path."""
        temp_path = os.path.join(tempfile.gettempdir(), filename)
        self._temp_files.append(temp_path)
        return temp_path
    
    async def _cleanup_temp_files(self):
        """Clean up temporary files."""
        for temp_file in self._temp_files:
            try:
                if os.path.exists(temp_file):
                    os.remove(temp_file)
            except Exception:
                pass
        self._temp_files.clear()
    
    def create_effect_preset(self, preset_name: str) -> List[EffectConfig]:
        """Create predefined effect presets."""
        presets = {
            "clean_voice": [
                EffectConfig("noise_gate", {"threshold": -50}),
                EffectConfig("highpass", {"frequency": 80}),
                EffectConfig("normalize", {"peak": -1.0})
            ],
            "professional_voice": [
                EffectConfig("noise_gate", {"threshold": -45}),
                EffectConfig("highpass", {"frequency": 85}),
                EffectConfig("compressor", {"threshold": -18, "ratio": 3}),
                EffectConfig("eq", {"low_gain": -1, "mid_gain": 2, "high_gain": 0}),
                EffectConfig("normalize", {"peak": -0.5})
            ],
            "warm_ambient": [
                EffectConfig("reverb", {"room_size": 0.4, "wet_level": 0.25}),
                EffectConfig("chorus", {"in_gain": 0.3}),
                EffectConfig("normalize", {"peak": -1.0})
            ]
        }
        
        return presets.get(preset_name, [])


class AudioEffectsError(MD2PPTError):
    """Raised when audio effects processing fails."""
    pass